/* @flow */

import config from 'core/config'
import { warn, cached } from 'core/util/index'
import { mark, measure } from 'core/util/perf'

import Vue from './runtime/index'
import { query } from './util/index'
import { compileToFunctions } from './compiler/index'
import { shouldDecodeNewlines, shouldDecodeNewlinesForHref } from './util/compat'

const idToTemplate = cached(id => {
  const el = query(id)
  return el && el.innerHTML
})

//执行挂载
const mount = Vue.prototype.$mount

Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {

  el = el && query(el)

  /* istanbul ignore if */
  if (el === document.body || el === document.documentElement) {
    process.env.NODE_ENV !== 'production' && warn(
      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
    )
    return this
  }

  const options = this.$options
  // resolve template/el and convert to render function
  // 没有写render方法
  if (!options.render) {
    let template = options.template
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          template = idToTemplate(template)
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !template) {
            warn(
              `Template element not found or is empty: ${options.template}`,
              this
            )
          }
        }
      } else if (template.nodeType) {
        template = template.innerHTML
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      template = getOuterHTML(el)
    }
    if (template) {
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile')
      }

      const { render, staticRenderFns } = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns

      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile end')
        measure(`vue ${this._name} compile`, 'compile', 'compile end')
      }
    }
  }

  /**
   *
   *   这个mount是另外一个地方编写的mount方法.$mount的职责很明确,就是为了生成render函数挂在到options上
   *
   *   线程走进了mount里,进入mountComponent函数,
   *
   *   组建了一个updateComponent函数
   *   updateComponent = function () {
          vm._update(vm._render(), hydrating);
       }

       定义了渲染watcher

       new Watcher(vm, updateComponent)

       在渲染watcher里面执行get()方法触发依赖收集,updateComponent被this.getter存储在watcher里,随即开始运行updateComponent函数
   *
   *   继续运行 vm._update(vm._render(), hydrating);
   *
   *   vm._update首次生成虚拟dom挂载,其余进行patch
   *
   *   vm._render()一运行,就会触发状态的get函数,触发依赖收集.vm存储watcher,watcher存储dep,dep又存储watcher
   *
   *   依赖收集完毕,下一次修改组件状态,触发组件的set方法.继而触发dep.notify(),直至最后触发watcher.run,又回到watcher.get方法
   *
   *   触发updateComponent,继而触发 vm._update(vm._render(), hydrating) , vm._render() 重新生成虚拟dom, vm._update进入patch挂载到页面上
   *
   *   由此可见 render 函数只负责生成虚拟dom,其他的不关它的事.
   *
   */
  return mount.call(this, el, hydrating);
}

/**
 * Get outerHTML of elements, taking care
 * of SVG elements in IE as well.
 */
function getOuterHTML (el: Element): string {
  if (el.outerHTML) {
    return el.outerHTML
  } else {
    const container = document.createElement('div')
    container.appendChild(el.cloneNode(true))
    return container.innerHTML
  }
}

Vue.compile = compileToFunctions

export default Vue
